/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) 2023-2024 Jeremy Long. All Rights Reserved.
 */
package io.github.jeremylong.openvulnerability.client.ghsa;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.Objects;

/**
 * A GitHub Security Advisory Identifier.
 *
 * <pre>
 * type SecurityAdvisoryIdentifier
 * </pre>
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder({"severity", "updatedAt", "firstPatchedVersion", "vulnerableVersionRange", "package"})
public class Vulnerability implements Serializable {

    /**
     * Serialization version UID.
     */
    private static final long serialVersionUID = -7379296334249178368L;
    @JsonProperty(value = "node", access = JsonProperty.Access.WRITE_ONLY)
    private VulnerabilityRecord node;

    /**
     * The first version containing a fix for the vulnerability.
     *
     * @return the first version containing a fix for the vulnerability.
     */
    @JsonProperty(value = "firstPatchedVersion")
    public PackageVersion getFirstPatchedVersion() {
        if (node == null) {
            return null;
        }
        return node.firstPatchedVersion;
    }

    /**
     * Internal setter for deserialization.
     *
     * @param packageVersion the version.
     */
    @JsonProperty(value = "firstPatchedVersion")
    void setFirstPatchedVersion(PackageVersion packageVersion) {
        if (node == null) {
            node = new VulnerabilityRecord();
        }
        node.firstPatchedVersion = packageVersion;
    }

    /**
     * A description of the vulnerable package.
     *
     * @return a description of the vulnerable package.
     */
    @JsonProperty(value = "package")
    public Package getPackage() {
        if (node == null) {
            return null;
        }
        return node.pkg;
    }

    /**
     * Internal setter for deserialization.
     *
     * @param pkg the package
     */
    @JsonProperty(value = "package")
    void setPackage(Package pkg) {
        if (node == null) {
            node = new VulnerabilityRecord();
        }
        node.pkg = pkg;
    }

    /**
     * The severity of the vulnerability within this package.
     *
     * @return the severity of the vulnerability within this package.
     */
    @JsonProperty(value = "severity")
    public Severity getSeverity() {
        if (node == null) {
            return null;
        }
        return node.severity;
    }

    /**
     * Internal setter for deserialization.
     *
     * @param severity severity
     */
    @JsonProperty(value = "severity")
    void setSeverity(Severity severity) {
        if (node == null) {
            node = new VulnerabilityRecord();
        }
        node.severity = severity;
    }

    /**
     * When the vulnerability was last updated.
     *
     * @return when the vulnerability was last updated.
     */
    @JsonProperty(value = "updatedAt")
    public ZonedDateTime getUpdatedAt() {
        if (node == null) {
            return null;
        }
        return node.updatedAt;
    }

    /**
     * Internal setter for deserialization.
     *
     * @param updatedAt updated date time
     */
    @JsonProperty(value = "updatedAt")
    void setUpdatedAt(ZonedDateTime updatedAt) {
        if (node == null) {
            node = new VulnerabilityRecord();
        }
        node.updatedAt = updatedAt;
    }

    /**
     * A string that describes the vulnerable package versions. This string follows a basic syntax with a few forms.
     *
     * <pre>
     *   `= 0.2.0` denotes a single vulnerable version.
     *   `&gt;= 1.0.8` denotes a version range up to and including the specified version
     *   `&lt; 0.1.11` denotes a version range up to, but excluding, the specified version
     *   `&gt;= 4.3.0, &lt; 4.3.5` denotes a version range with a known minimum and maximum version.
     *   `&gt;= 0.0.1` denotes a version range with a known minimum, but no known maximum
     * </pre>
     *
     * @return the range.
     */
    @JsonProperty(value = "vulnerableVersionRange")
    public String getVulnerableVersionRange() {
        if (node == null) {
            return null;
        }
        return node.vulnerableVersionRange;
    }

    /**
     * Internal setter for deserialization.
     *
     * @param range the range
     */
    @JsonProperty(value = "vulnerableVersionRange")
    void setVulnerableVersionRange(String range) {
        if (node == null) {
            node = new VulnerabilityRecord();
        }
        node.vulnerableVersionRange = range;
    }

    @Override
    public String toString() {
        return "Vulnerability[" + node + ']';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Vulnerability that = (Vulnerability) o;
        return Objects.equals(node, that.node);
    }

    @Override
    public int hashCode() {
        return Objects.hash(node);
    }

    /**
     * An individual vulnerability within an Advisory.
     *
     * <pre>
     * type SecurityVulnerability
     * </pre>
     */
    @JsonIgnoreProperties(ignoreUnknown = true)
    static class VulnerabilityRecord implements Serializable {

        /**
         * Serialization version UID.
         */
        private static final long serialVersionUID = -3820979074950827855L;

        @JsonProperty("firstPatchedVersion")
        private PackageVersion firstPatchedVersion;

        @JsonProperty("package")
        private Package pkg;

        @JsonProperty("severity")
        private Severity severity;

        @JsonProperty("updatedAt")
        private ZonedDateTime updatedAt;

        @JsonProperty("vulnerableVersionRange")
        private String vulnerableVersionRange;

        @Override
        public String toString() {
            return "VulnerabilityRecord{" + "firstPatchedVersion=" + firstPatchedVersion + ", pkg=" + pkg
                    + ", severity=" + severity + ", updatedAt=" + updatedAt + ", vulnerableVersionRange='"
                    + vulnerableVersionRange + '\'' + '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            VulnerabilityRecord that = (VulnerabilityRecord) o;
            return Objects.equals(firstPatchedVersion, that.firstPatchedVersion) && Objects.equals(pkg, that.pkg)
                    && severity == that.severity && Objects.equals(updatedAt, that.updatedAt)
                    && Objects.equals(vulnerableVersionRange, that.vulnerableVersionRange);
        }

        @Override
        public int hashCode() {
            return Objects.hash(firstPatchedVersion, pkg, severity, updatedAt, vulnerableVersionRange);
        }
    }
}
